package br.com.ac.fatureweb.integration.business;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import br.com.ac.fatureweb.integration.billing.VolumeVO;
import br.com.ac.fatureweb.integration.model.ConfiguracaoSaaeVO;
import br.com.ac.fatureweb.integration.model.ImovelVO;
import br.com.ac.fatureweb.integration.model.OcorrenciaVO;
import br.com.ac.fatureweb.integration.model.OcorrenciaVO.EnumTypeCobranca;
import br.com.ac.fatureweb.integration.model.SaldoDebitoCreditoVO;
import br.com.ac.fatureweb.integration.model.SaldoDebitoCreditoVO.EnumTypeSaldo;
import br.com.ac.fatureweb.integration.model.VolumeSaldo;

/**
 * Calcular o consumo em metros cúbicos para um imóvel.
 * 
 * @autor Aldivone Correia - 10/01/2015
 *
 */
public class CalcularConsumo {

	private ImovelVO imovel;

	private Integer codProduto;

	private List<VolumeSaldo> lstVolumeSaldo = new ArrayList<VolumeSaldo>();

	public CalcularConsumo(ImovelVO imovel, Integer codProduto) {
		super();
		this.imovel = imovel;
		this.codProduto = codProduto;
	}

	/**
	 * Chamar a execução do cálculo do consumo para imóvel, de acordo com
	 * leitura ou ocorrência informada.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * 
	 * @param leituraAtual
	 * @param leituraAnterior
	 * @param codOcorrenciaCampo
	 * @param mediaImovel
	 * @param isTemHidrometro
	 * @param typeCobrancaConsumoZerado
	 * @param minimoTarifa
	 * @param saldoDebitoCredito
	 * @param lstOcorrenciaVO
	 * 
	 * @return
	 */
	public VolumeVO calcular(Long leituraAtual, Long leituraAnterior,
			Long codOcorrenciaCampo, Long mediaImovel, boolean isTemHidrometro,
			EnumTypeCobranca typeCobrancaConsumoZerado, Long minimoTarifa,
			List<SaldoDebitoCreditoVO> lstSaldoDebitoCredito,
			List<OcorrenciaVO> lstOcorrenciaVO) {

		VolumeVO volumeVO = new VolumeVO();

		Long consumoMes = 0L;

		leituraAtual = leituraAtual == null ? 0L : leituraAtual;

		leituraAnterior = leituraAnterior == null ? 0L : leituraAnterior;

		mediaImovel = mediaImovel == null ? 0L : mediaImovel;

		if (isTemHidrometro) {
			OcorrenciaVO ocorrenciaVO = getOcorrencia(lstOcorrenciaVO,
					codOcorrenciaCampo);

			consumoMes = calcularConsumo(leituraAtual, ocorrenciaVO,
					leituraAnterior, mediaImovel, typeCobrancaConsumoZerado,
					minimoTarifa);

		} else {
			codOcorrenciaCampo = 2L;

			OcorrenciaVO ocorrenciaVO = getOcorrencia(lstOcorrenciaVO,
					codOcorrenciaCampo);

			if (ocorrenciaVO == null) {
				throw new IllegalArgumentException(
						"Para calcular consumo de imóvel que não tem hidrômetro é "
								+ "necessário existe a ocorrência: 2 - IMÓVEL SEM HIDRÔMETRO");
			}

			consumoMes = calcularConsumo(leituraAtual, ocorrenciaVO,
					leituraAnterior, mediaImovel, typeCobrancaConsumoZerado,
					minimoTarifa);
		}

		volumeVO.setConsumoApurado(consumoMes);

		/*
		 * Ajustar o volume de acordo com o saldo enviado pelo SAAE. O Saldo L
		 * será calculado somente quando a leitura for normal.
		 */
		if (lstSaldoDebitoCredito != null && !lstSaldoDebitoCredito.isEmpty()) {

			for (SaldoDebitoCreditoVO saldoDebitoCredito : lstSaldoDebitoCredito) {

				if (saldoDebitoCredito.getConsumo() != null) {

					consumoMes = calcularSaldoDebitoCredito(consumoMes,
							saldoDebitoCredito, minimoTarifa,
							codOcorrenciaCampo, leituraAtual);
				}
			}
		}

		long consumoMedido = calcularConsumoMedido(leituraAtual,
				leituraAnterior);

		// Caso não tenha leitura atual colocar a leitura anterior na atual.
		if (leituraAtual == 0L) {
			leituraAtual = leituraAnterior;

			if (leituraAnterior == null || leituraAnterior.equals(0L)) {
				leituraAtual = 0L;
			}
		}

		if (consumoMes == null || consumoMes < 0) {
			consumoMes = 0L;
		}

		// Calcular a quantidade de dias de consumo.
		Integer diasConsumo = calcularDiasConsumo();

		volumeVO.setCodOcorrencia1(codOcorrenciaCampo);
		volumeVO.setCodOcorrencia2(gerarAlertaAoSaae(codOcorrenciaCampo));
		volumeVO.setConsumoFaturado(consumoMes);
		volumeVO.setConsumoMedido(consumoMedido);
		volumeVO.setLeituraAtual(leituraAtual);
		volumeVO.setLeituraAnterior(leituraAnterior);
		volumeVO.setDiasConsumo(diasConsumo);

		volumeVO.setIdCarga(imovel.getIdCarga());
		volumeVO.setInscricao(imovel.getInscricao());
		volumeVO.setCodProduto(codProduto);
		volumeVO.setLstVolumeSaldo(lstVolumeSaldo);

		return volumeVO;
	}

	private Integer calcularDiasConsumo() {
		Long dataLeituraAnterior = imovel.getDataLeituraAnterior();

		if (dataLeituraAnterior == null || dataLeituraAnterior.equals(0L)) {
			dataLeituraAnterior = imovel.getDataInstalacaoHidrometro();
		}

		Integer diasConsumo = getDiferencaEmDias(new Date().getTime(),
				dataLeituraAnterior);

		if (diasConsumo > 31) {
			diasConsumo = 30;
		}

		if (diasConsumo == 0) {
			diasConsumo = 30;
		}

		return diasConsumo;
	}

	private long calcularConsumoMedido(Long leituraAtual, Long leituraAnterior) {
		long consumoMedido = leituraAtual - leituraAnterior;

		if (consumoMedido < 0) {
			consumoMedido = 0;
		}
		return consumoMedido;
	}

	/**
	 * Executar calculo de saldo de acordo com o tipo e consumo mensal.
	 * 
	 * tipo = "H" saldo devedor, retirada de hidrômetro, deverá ser acrescentado
	 * ao consumo da água.
	 * 
	 * tipo = "L" saldo acumulado por leitura não realizada reduzir o consumo da
	 * água, não podendo ser menor que o mínimo da tarifa, só será lançado se
	 * houver leitura.
	 * 
	 * tipo = "M" saldo credor, deve ser retirado do consumo da ligação, zerando
	 * o consumo não cobrar a água.
	 * 
	 * 
	 * 4. Mensagens originadas no faturamento:
	 * 
	 *  Saldo tipo “M”, o consumo será subtraído do consumo mensal e este
	 * procedimento deve ser complementado gerando-se uma mensagem individual de
	 * “Foi subtraído 99m3 do seu consumo, proveniente de saldo em metros
	 * cúbicos.”;
	 * 
	 *  Saldo tipo “L”, o consumo será subtraído do consumo mensal e este
	 * procedimento deve ser complementado gerando-se uma mensagem individual de
	 * “Foi subtraído 99m3 do seu consumo, proveniente de Leituras não
	 * Realizadas.”;
	 * 
	 * O Saldo L será calculado somente quando a leitura for normal.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @param consumoMes
	 * @param saldoDebitoCredito
	 * @param minimoTarifa
	 * @param codOcorrenciaCampo
	 * @param leituraAtual
	 * @return
	 */
	private Long calcularSaldoDebitoCredito(Long consumoMes,
			SaldoDebitoCreditoVO saldoDebitoCredito, Long minimoTarifa,
			Long codOcorrenciaCampo, Long leituraAtual) {
		if (saldoDebitoCredito != null
				&& saldoDebitoCredito.getConsumo() != null
				&& saldoDebitoCredito.getTipo() != null) {

			EnumTypeSaldo typeSaldo = EnumTypeSaldo.valueOf(saldoDebitoCredito
					.getTipo());

			if (typeSaldo != null) {
				switch (typeSaldo) {

				/*
				 * tipo = "H" saldo devedor, retirada de hidrômetro, deverá ser
				 * acrescentado ao consumo da água.
				 */
				case H: {
					consumoMes += saldoDebitoCredito.getConsumo();
					break;
				}

				/*
				 * tipo = "L" saldo acumulado por leitura não realizada. Reduzir
				 * o consumo da água, não podendo ser menor que o mínimo da
				 * tarifa, só será lançado se houver leitura. O Saldo L será
				 * calculado somente quando a leitura for normal.
				 * 
				 * O imóvel : 102713 teve leitura com hidrometro parado e não
				 * foi lançado o saldo L. obs : o saldo L só não é lançado
				 * quando não há leitura, como houve leitura para este imóvel,
				 * mesmo estando com hidrometro parado, foi reduzido 2m dos 12
				 * consumidos.
				 */
				case L: {
					boolean isLeituraNormal = codOcorrenciaCampo == null
							|| codOcorrenciaCampo.equals(0L);

					boolean isLeituraNormalComOcorrencia = codOcorrenciaCampo != null
							&& codOcorrenciaCampo.intValue() >= 0
							&& leituraAtual != null
							&& leituraAtual.intValue() > 0;

					if (isLeituraNormal || isLeituraNormalComOcorrencia) {

						Long consumoTemp = consumoMes
								- saldoDebitoCredito.getConsumo();
						consumoMes = consumoTemp;
						Long consumoDeduzido = saldoDebitoCredito.getConsumo();

						if (minimoTarifa.intValue() > consumoTemp.intValue()) {
							consumoMes = minimoTarifa;
							
							gerarRetornoSaldo(consumoMes, typeSaldo,
									consumoMes, consumoDeduzido);
						} else {
							if (consumoDeduzido > consumoMes ) {
								consumoDeduzido = consumoMes;
							}
							
							gerarRetornoSaldo(consumoMes, typeSaldo,
									consumoTemp, consumoDeduzido);
						}
					}
					break;
				}

				/*
				 * tipo = "M" saldo credor, deve ser retirado do consumo da
				 * ligação, zerando o consumo não cobrar a água.
				 */
				case M: {
					if (consumoMes.intValue() < saldoDebitoCredito.getConsumo()
							.intValue()) {

						consumoMes = 0L;

						lstVolumeSaldo.add(new VolumeSaldo(typeSaldo,
								saldoDebitoCredito.getConsumo()));
					} else {

						consumoMes -= saldoDebitoCredito.getConsumo();

						lstVolumeSaldo.add(new VolumeSaldo(typeSaldo,
								consumoMes));
					}
					break;
				}
				}
			}
		}

		return consumoMes;
	}

	private void gerarRetornoSaldo(Long consumoMes, EnumTypeSaldo typeSaldo,
			Long consumoTemp, Long consumoDeduzido) {
		
		lstVolumeSaldo.add(new VolumeSaldo(typeSaldo, consumoTemp,
				consumoDeduzido));
	}

	private Long gerarAlertaAoSaae(Long codOcorrenciaCampo) {

		// Retornar por default a ocorrência: 0 - Leitura normal.
		Long codOcorrenciaAlerta = 0L;

		if (codOcorrenciaCampo != null) {

			/*
			 * 45;"HIDROMETRO DANIFICADO" 47;"HIDROMETRO SOTERRADO"
			 * 50;"HIDROMETRO PARADO" 51;"VERIFICAR LEIT. ANT."
			 * 52;"HIDROMETRO INVERTIDO" 53;"HIDROMETRO VIRADO"
			 * 54;"HIDROM INVERT/VIRADO" 55;"ROTEIRO FORA DE SEQ."
			 */
			switch (codOcorrenciaCampo.intValue()) {
			case 45:
			case 47:
			case 50:
			case 51:
			case 52:
			case 53:
			case 54:
			case 55: {
				codOcorrenciaAlerta = 0L;
				break;
			}
			}
		}

		return codOcorrenciaAlerta;
	}

	/**
	 * Localizar na lista de ocorrências de leitura uma ocorrência de acordo com
	 * seu código.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @param lstOcorrenciaVO
	 * @param codOcorrencia
	 * @return
	 */
	public static OcorrenciaVO getOcorrencia(
			List<OcorrenciaVO> lstOcorrenciaVO, long codOcorrencia) {
		if (lstOcorrenciaVO != null && !lstOcorrenciaVO.isEmpty()) {
			for (OcorrenciaVO ocorrenciaVO : lstOcorrenciaVO) {
				if (ocorrenciaVO.getCodigo().equals(codOcorrencia)) {
					return ocorrenciaVO;
				}
			}
		}
		return null;
	}

	/**
	 * 
	 * @param leituraAtual
	 * @param ocorrenciaVO
	 * @param leituraAnterior
	 * @param mediaImovel
	 * @param typeCobrancaConsumoZerado
	 * @param minimoTarifa
	 * @return
	 */
	private Long calcularConsumo(Long leituraAtual, OcorrenciaVO ocorrenciaVO,
			Long leituraAnterior, Long mediaImovel,
			EnumTypeCobranca typeCobrancaConsumoZerado, Long minimoTarifa) {
		Long consumoMes = 0L;

		if (ocorrenciaVO != null && ocorrenciaVO.getCodigo() != null) {
			if (ocorrenciaVO.getCodigo() != null) {

				switch (ocorrenciaVO.getCodigo().intValue()) {

				case 0: {

					/*
					 * 3.2.2.1 – código 0(leitura normal) – consumo zerado , o
					 * consumo deve ser calculado de acordo com a configuração
					 * encontrada no campo "FAT_CONSZERADO" do objeto de
					 * configuração:
					 * 
					 * 3.2.2.1.1 - FAT_CONSZERADO = 0, consumo recebe a média do
					 * imóvel;
					 * 
					 * 3.2.2.1.2 - FAT_CONSZERADO = 1, consumo recebe o mínimo
					 * da tarifa.
					 */

					/*
					 * Leitura normal a ocorrência do tipo 2 não deve ser zero e
					 * o consumo calculado leitura atual - anterior;
					 */
					consumoMes = leituraAtual - leituraAnterior;

					if (consumoMes < 0L) {
						consumoMes = 0L;
					}

					if (consumoMes.equals(0L)) {
						consumoMes = calcularTypeCobranca(mediaImovel,
								typeCobrancaConsumoZerado, consumoMes);
					}

					break;
				}

				case 50: {
					/*
					 * Leitura atual igual à leitura anterior, a ocorrência tipo
					 * 2 deve ser informada: código 0 para consumo zerado ou
					 * código 50 para hidrômetro parado, se o código da
					 * ocorrência for zero, verificar na configuração o tipo de
					 * cobrança para consumo zerado, se o código for 50, cobrar
					 * pela média;
					 */
					consumoMes = mediaImovel;

					break;
				}

				case 53: {
					/*
					 * código 53 (hidrômetro virado)- o consumo é calculado
					 * (leitura atual + 10000) - leitura anterior;
					 */
					consumoMes = (leituraAtual + 10000L) - leituraAnterior;
					break;
				}

				case 54: {
					/*
					 * código 54 (hidrômetro invertido/virado) o consumo é
					 * calculado (leitura anterior + 10000) - leitura atual.
					 */
					consumoMes = (leituraAnterior + 10000L) - leituraAtual;
					break;
				}

				default: {
					/*
					 * Leitura normal a ocorrência do tipo 2 não deve ser zero e
					 * o consumo calculado leitura atual - anterior;
					 */
					consumoMes = leituraAtual - leituraAnterior;

					if (consumoMes < 0L) {
						consumoMes = 0L;
					}

					/*
					 * 18 – Campo “FLG_COBRANCA”: - Este campo só deve ser
					 * considerado quando o campo “FLG_OCORRENCIA” for 0 (Zero).
					 * Preenchido com 0 (Zero) – Cobrar o consumo mínimo da
					 * tarifa. Preenchido com 1 (Um) – Cobrar a média da
					 * ligação.
					 */
					if (ocorrenciaVO.getFlgTipoCobranca() != null) {
						EnumTypeCobranca typeCobrancaConsumoZerado2 = EnumTypeCobranca
								.valueOf(ocorrenciaVO.getFlgTipoCobranca());

						consumoMes = calcularTypeCobranca(mediaImovel,
								typeCobrancaConsumoZerado2, minimoTarifa);
					}
					break;
				}
				}
			}
		}

		return consumoMes;
	}

	/**
	 * 
	 * A maioria das ocorrências são cobrada pela média FLG_COBRANCA = 1 , se o
	 * consumo for menor que o mínimo da tarifa então é cobrado o mínimo
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @param mediaImovel
	 * @param typeCobrancaConsumoZerado
	 * @param minimoTarifa
	 * @return
	 */
	private Long calcularTypeCobranca(Long mediaImovel,
			EnumTypeCobranca typeCobrancaConsumoZerado, Long minimoTarifa) {

		Long consumoMes = 0L;

		if (typeCobrancaConsumoZerado != null) {
			switch (typeCobrancaConsumoZerado) {
			case MEDIA_IMOVEL: {
				consumoMes = mediaImovel;
				break;
			}
			case MINIMO_TARIFA: {
				consumoMes = minimoTarifa;
				break;
			}
			}
		}
		return consumoMes;
	}

	/*
	 * 3 – em uma única leitura pode haver 2 ocorrências, exemplo:
	 * 
	 * O leiturista informa que o imóvel não possui hidrômetro (ocorrência 1 =
	 * código 2) , e que o roteiro do mesmo está fora de
	 * 
	 * sequencia ( ocorrência 2 = código 55).
	 * 
	 * Se você preferir, pode informar apenas uma ocorrência, para isto:
	 * 
	 * 3.1 - devolva a ocorrência 2 sempre 0(zero);
	 * 
	 * 3.2 - se houver leitura então a ocorrência 1 é 0(zero), exceto se :
	 * 
	 * 3.2.1 – a leitura atual for menor que a anterior, poderá informar as
	 * ocorrências:
	 * 
	 * 3.2.1.1 – código 51 (verificar leit. Ant) . o imóvel volta para ser
	 * analisado;
	 * 
	 * 3.2.1.2 – código 52 (hidrômetro Invertido)- o consumo é calculado leitura
	 * anterior – leitura atual;
	 * 
	 * 3.2.1.3 – código 53 (hidrômetro virado)- o consumo é calculado (leitura
	 * atual + 10000) - leitura anterior;
	 * 
	 * 3.2.1.4 – código 54(hidrômetro invertido/virado) o consumo é calculado
	 * (leitura anterior + 10000) - leitura atual.
	 * 
	 * 3.2.2 – a leitura atual for igual à leitura anterior:
	 * 
	 * 3.2.2.1 – código 0(leitura normal) – consumo zerado , o consumo deve ser
	 * calculado de acordo com a configuração encontrada no campo
	 * "FAT_CONSZERADO" do objeto de configuração:
	 * 
	 * 3.2.2.1.1 - FAT_CONSZERADO = 0, consumo recebe a média do imóvel;
	 * 
	 * 3.2.2.1.2 - FAT_CONSZERADO = 1, consumo recebe o mínimo da tarifa.
	 * 
	 * 3.2.2.2 – código 50(hidrômetro parado), o consumo recebe a média do
	 * imóvel.
	 */
	public static ValidarLeitura validarLeitura(boolean isTemHidrometro,
			Long leituraAtual, Long leituraAnterior,
			ConfiguracaoSaaeVO configuracaoSaaeVO) {

		ValidarLeitura validacao = new ValidarLeitura();

		if (leituraAtual != null) {
			validacao.codOcorrencia = 0L;
		}

		leituraAtual = leituraAtual == null ? 0L : leituraAtual;

		leituraAnterior = leituraAnterior == null ? 0L : leituraAnterior;

		if (leituraAtual.intValue() < leituraAnterior.intValue()) {
			validacao.isLeituraMenor = true;
		}

		else if (leituraAtual.equals(leituraAnterior)) {
			validacao.isLeituraIgual = true;
		}

		return validacao;
	}

	private int getDiferencaEmDias(final Long dataMaior, final Long dataMenor) {
		if (dataMaior == null || dataMenor == null) {
			return 0;
		}

		final Calendar instanceMaior = Calendar.getInstance();
		instanceMaior.setTimeInMillis(dataMaior);

		final Calendar instanceMenor = Calendar.getInstance();
		instanceMenor.setTimeInMillis(dataMenor);

		final long timeInMillisMenor = instanceMenor.getTimeInMillis();

		final long timeInMillisMaior = instanceMaior.getTimeInMillis();

		if (timeInMillisMenor > timeInMillisMaior) {
			return 0;
		}

		/*
		 * Necessário para poder calcular a diferença dos dias entre vários
		 * anos.
		 */
		final long retorno = (timeInMillisMaior - timeInMillisMenor)
				/ (24 * 60 * 60 * 1000);

		final Long long1 = Long.valueOf(retorno);

		return long1.intValue();
	}

	/**
	 * Validar as informações de leitura informadas pelo leiturista.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 *
	 */
	public static class ValidarLeitura {

		/*
		 * Caso a leitura atual não seja menor ou igual a anterior devolver a
		 * ocorrência 0(Zero) - Leitura Normal.
		 */
		public Long codOcorrencia;

		/*
		 * Solicitar ocorrência se leitura atual é menor que a leitura anterior.
		 */
		public boolean isLeituraMenor;

		/*
		 * Solicitar ocorrência se leitura atual é igual a leitura anterior.
		 */
		public boolean isLeituraIgual;

		/**
		 * 
		 */
		public List<Long> lstOcorrenciaLeituraMenor = new ArrayList<Long>();
		{
			lstOcorrenciaLeituraMenor.add(51L);
			lstOcorrenciaLeituraMenor.add(52L);
			lstOcorrenciaLeituraMenor.add(53L);
			lstOcorrenciaLeituraMenor.add(54L);
		};

		public ValidarLeitura() {
			super();
		}

	}

}
